﻿#region Copyright

// Majesty of Omega - The next evolution of macro management space strategy
// 
// Copyright 2007-2009 André Claaßen
// 
// This file is protected by the terms and conditions of the
// Microsoft Community License (Ms-CL 1.1), a copy of which should
// have been distributed along with this software. If not,
// you may find the license information at the following URL:
// 
// http://www.microsoft.com/resources/sharedsource/licensingbasics/communitylicense.mspx

#endregion

#region Imports

using System.Collections.Generic;
using System.Linq;
using DesignByContract;
using Majesty_of_Omega.Config;
using Majesty_of_Omega.Model.Research;

#endregion

namespace Majesty_of_Omega.Logic.Calculate
{
    /// <summary>
    /// This class distributes research points and increases tech levels and unlocks 
    /// new technologies
    /// </summary>
    public class FundamentalResearchCalculator : GameCalculator
    {
        /// <summary>
        /// Gets the research manager from the current player
        /// </summary>
        /// <value>The research manager.</value>
        protected ResearchManager ResearchManager
        {
            get
            {
                return CalculateInfo.Player.ResearchManager;
            }
        }


        #region GameCalculator Members

        /// <summary>
        /// Called when [calculate].
        /// </summary>
        /// <param name="calculateInfo">The calculate info.</param>
        protected override void OnCalculate(CalculateInfo calculateInfo)
        {
            DistributeFundamentalResearchPoints(
                calculateInfo.Player.ResearchManager.SumOfFundamentalResearchPoints,
                calculateInfo.Player.ResearchManager,
                calculateInfo);
            UnlockDependendTechFields(calculateInfo);
            UnlockNewTechnologies();
        }

        #endregion

        /// <summary>
        /// Distributes the research points over the various tech fields
        /// </summary>
        /// <param name="researchPoints">The research points.</param>
        /// <param name="manager">The manager.</param>
        /// <param name="calculateInfo">The calculate info.</param>
        public void DistributeFundamentalResearchPoints(int researchPoints, ResearchManager manager, CalculateInfo calculateInfo)
        {
            int remainingPoints = researchPoints;
            int iLast = manager.TechSchoolResearchs.Count;
            foreach (var techSchoolResearch in manager.TechSchoolResearchs)
            {
                int pointsForSchool = (iLast == 1)
                                          ? remainingPoints
                                          : techSchoolResearch.ResearchPercentage * researchPoints / 100;
                DistributeResearchPointsForTechSchool(pointsForSchool, manager, techSchoolResearch, calculateInfo);
                iLast--;
                remainingPoints -= pointsForSchool;
            }
            Ensure.IsTrue(remainingPoints == 0);
        }

        /// <summary>
        /// Distributes the research points for tech school.
        /// </summary>
        /// <param name="researchPointsForTechSchool">The research points for tech school.</param>
        /// <param name="manager"></param>
        /// <param name="techSchoolResearch">The tech school research.</param>
        /// <param name="calculateInfo">The calculate info.</param>
        private void DistributeResearchPointsForTechSchool(int researchPointsForTechSchool,
                                                           ResearchManager manager,
                                                           TechSchoolResearch techSchoolResearch,
                                                           CalculateInfo calculateInfo)
        {
            int remainingPoints = researchPointsForTechSchool;
            IEnumerable<TechFieldResearch> researchs =
                manager.GetTechFieldResearchForTechSchool(techSchoolResearch.TechSchool);
            int sumPercentage = researchs.Sum(r => r.ResearchPercentage);
            Require.AreEqual(100, sumPercentage);
            int iLast = researchs.Count();
            foreach (var techFieldResearch in researchs)
            {
                int pointsForField = (iLast == 1)
                                         ? remainingPoints
                                         : techFieldResearch.ResearchPercentage * researchPointsForTechSchool / 100;
                iLast--;
                remainingPoints -= pointsForField;
                DistributeResearchPointsForTechField(pointsForField, techFieldResearch, calculateInfo);
            }
            Ensure.IsTrue(remainingPoints == 0);
        }

        /// <summary>
        /// Distributes the research points for tech field.
        /// </summary>
        /// <param name="increaseOfResearchPoints">The increase of research points.</param>
        /// <param name="techFieldResearch">The tech field research.</param>
        /// <param name="calculateInfo">The turn.</param>
        public void DistributeResearchPointsForTechField(int increaseOfResearchPoints,
                                                         TechFieldResearch techFieldResearch,
                                                         CalculateInfo calculateInfo)
        {
            techFieldResearch.ResearchPointsAccumulated += increaseOfResearchPoints;

            while (true)
            {
                if (techFieldResearch.ResearchPointsAccumulated < techFieldResearch.ThresholdForNextLevel)
                {
                    techFieldResearch.RecalculateProgressInPercent();
                    break;
                }
                IncreaseTechLevel(techFieldResearch, calculateInfo);
            }
        }

        /// <summary>
        /// Unlocks the new tech level.
        /// </summary>
        /// <param name="techFieldResearch">The tech field research.</param>
        /// <param name="calculateInfo">The report turn.</param>
        /// <example>
        /// 	<![CDATA[
        /// %race1% have made a research breakthrough in the field of %subject%.
        /// You are now at level %level%
        /// ]]>
        /// </example>
        private static void IncreaseTechLevel(TechFieldResearch techFieldResearch, CalculateInfo calculateInfo)
        {
            techFieldResearch.IncreaseTechLevel();
            calculateInfo.ReportTurn.AddSitRepEntry(SitReportType.ResearchBreakthroughOnField, 
                                                    techFieldResearch.TechField,
                                                    "race1", calculateInfo.Player.Race.Name,
                                                    "subject", techFieldResearch.TechField.Name,
                                                    "level", techFieldResearch.TechLevel);
        }

        /// <summary>
        /// Unlocks the new tech level.
        /// </summary>
        /// <param name="techFieldResearch">The tech field research.</param>
        /// <param name="calculateInfo">The report turn.</param>
        /// <example>
        /// 	<![CDATA[
        /// %race1% have made a research breakthrough in the field of %subject%.
        /// You are now at level %level%
        /// ]]>
        /// </example>
        private static void UnlockNewTechField(TechFieldResearch techFieldResearch, CalculateInfo calculateInfo)
        {
            calculateInfo.Player.ResearchManager.UnlockTechField(techFieldResearch);
            calculateInfo.ReportTurn.AddSitRepEntry(SitReportType.ResearchUnlockTechField, 
                                                    techFieldResearch.TechField,
                                                    "race1", calculateInfo.Player.Race.Name,
                                                    "subject", techFieldResearch.TechField.Name,
                                                    "level", techFieldResearch.TechLevel);
        }

        /// <summary>
        /// Unlocks the dependend tech fields.
        /// </summary>
        /// <param name="calculateInfo">The calculate info.</param>
        private static void UnlockDependendTechFields(CalculateInfo calculateInfo)
        {
            var researchedTechFields =
                DiscoverUnlockableTechFields(calculateInfo.Player.ResearchManager);

            foreach (var researchedTechField in researchedTechFields)
            {
                UnlockNewTechField(researchedTechField, calculateInfo);
            }
        }

        /// <summary>
        /// This linq function looks after tech fields, which are unlockable.
        /// </summary>
        /// <param name="researchManager">The research manager.</param>
        /// <returns></returns>
        public static IEnumerable<TechFieldResearch> DiscoverUnlockableTechFields(ResearchManager researchManager)
        {
            return from techFieldResearch in researchManager.TechFieldResearchs
                   where !techFieldResearch.Researched &&
                         AreAllTechFieldDependenciesResearched(researchManager, techFieldResearch.TechField.TechFieldDependencies)
                   select techFieldResearch;
        }

        /// <summary>
        /// Are all dependend tech fields fully researched?
        /// </summary>
        /// <param name="researchManager">The research manager.</param>
        /// <param name="techFieldDependencies">The tech field dependencies.</param>
        /// <returns></returns>
        public static bool AreAllTechFieldDependenciesResearched(ResearchManager researchManager, IEnumerable<TechFieldDependency> techFieldDependencies)
        {
            return techFieldDependencies.Aggregate(true,
                (resAggregate, td) =>
                {
                    if (!resAggregate) return false;
                    var dependendResearch = researchManager.GetTechFieldResearch(td.TechField);
                    return dependendResearch.Researched && dependendResearch.TechLevel >= td.TechLevel;
                });
        }

        /// <summary>
        /// Unlocks new technologies.
        /// </summary>
        public void UnlockNewTechnologies()
        {
            var unlockableTechnologies =
                DiscoverUnlockableTechnologies(ResearchManager);
            foreach (var technology in unlockableTechnologies)
            {
                ResearchManager.CreateNewAvailableResearch(technology);
                SitReportTurn.AddSitRepEntry
                    (SitReportType.NewTechnologyUncovered, 
                    technology, 
                    "subject", technology.Name,
                    "field", technology.TechFieldDependencies[0].TechField.Name);
            }
        }

        /// <summary>
        /// Discovers the unlockable technologies.
        /// </summary>
        /// <param name="manager">The manager.</param>
        /// <returns></returns>
        public static IEnumerable<TechApplication> DiscoverUnlockableTechnologies(ResearchManager manager)
        {
            return from techApplication in manager.GetNextUpcomingTechnologies(0)
                   where
                       AreAllTechFieldDependenciesResearched(manager,
                                                             techApplication.TechFieldDependencies)
                   select techApplication;
        }
    }
}